package mongo

import (
	"context"
	"fmt"
	"time"

	"engine/eng_cfg"
	"gitee.com/ameise84/e3utils/errors"
	"github.com/spf13/viper"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type cli struct {
	db *mongo.Database
}

func (ts *cli) Select(table string, wheres []FV) (outs []map[string]any, err error) {
	collection := ts.db.Collection(table)
	filter := make([]bson.E, 0, len(wheres))
	for _, where := range wheres {
		filter = append(filter, bson.E{Key: where.Field, Value: where.Value})
	}

	p := bson.M{"_id": 0} // 设置投影来排除_id字段
	opt := options.Find().SetProjection(p)
	ctx := context.TODO()
	cur, err := collection.Find(ctx, filter, opt)
	if err != nil && !errors.Is(err, mongo.ErrNoDocuments) {
		return nil, err
	}
	defer func() {
		_ = cur.Close(ctx)
	}()
	for cur.Next(ctx) {
		var result bson.M
		err := cur.Decode(&result)
		if err != nil {
			return nil, err
		}
		outs = append(outs, result)
	}
	return outs, nil
}

func (ts *cli) Insert(table string, data []FV) (any, error) {
	collection := ts.db.Collection(table)
	bsd := make([]bson.E, 0, len(data))
	for _, d := range data {
		bsd = append(bsd, bson.E{Key: d.Field, Value: d.Value})
	}
	r, err := collection.InsertOne(context.TODO(), bsd)
	if err != nil {
		return nil, err
	}
	return r.InsertedID, err
}

func (ts *cli) InsertOrUpdate(table string, wheres []FV, data []FV) (any, error) {
	panic("implement me")
	return 0, nil
}

func (ts *cli) InsertMany(table string, docs []Doc) error {
	collection := ts.db.Collection(table)
	dbDocs := make([]any, 0, len(docs))
	for _, doc := range docs {
		bsd := make([]bson.E, 0, len(doc.Wheres)+len(doc.Data))
		for _, where := range doc.Wheres {
			bsd = append(bsd, bson.E{Key: where.Field, Value: where.Value})
		}
		for _, date := range doc.Data {
			bsd = append(bsd, bson.E{Key: date.Field, Value: date.Value})
		}
		dbDocs = append(dbDocs, bsd)
	}
	r, err := collection.InsertMany(context.TODO(), dbDocs)
	if err != nil {
		err = errors.New(fmt.Sprintf("mongo insert many:%+v success ids:%v", docs, r))
	}
	return err
}

func (ts *cli) Update(table string, wheres []FV, data []FV) error {
	collection := ts.db.Collection(table)
	filter := make([]bson.E, 0, len(wheres))
	update := make([]bson.E, 0, len(data))
	for _, where := range wheres {
		filter = append(filter, bson.E{Key: where.Field, Value: where.Value})
	}
	for _, date := range data {
		update = append(update, bson.E{Key: date.Field, Value: date.Value})
	}
	opts := options.Update().SetUpsert(true)
	_, err := collection.UpdateOne(context.TODO(), filter, bson.D{{"$set", update}}, opts)
	if err != nil {
		err = errors.Wrap(err, fmt.Sprintf("mysql update failed: table:%s wheres:%v data:%v", table, wheres, data))
	}
	return err
}

func (ts *cli) UpdateMany(table string, docs []Doc) error {
	collection := ts.db.Collection(table)
	models := make([]mongo.WriteModel, 0, len(docs))
	for _, doc := range docs {
		filter := make([]bson.E, 0, len(doc.Wheres))
		update := make([]bson.E, 0, len(doc.Data))
		for _, where := range doc.Wheres {
			filter = append(filter, bson.E{Key: where.Field, Value: where.Value})
		}
		for _, date := range doc.Data {
			update = append(update, bson.E{Key: date.Field, Value: date.Value})
		}
		one := mongo.NewUpdateOneModel().SetUpsert(true).SetFilter(filter).SetUpdate(bson.D{{"$set", update}})
		models = append(models, one)
	}
	r, err := collection.BulkWrite(context.TODO(), models)
	if r.UpsertedCount != 0 && err == nil {
		return errors.New(fmt.Sprintf("mongo update many failed replace to insert: %v", r.UpsertedIDs))
	}
	return err
}

func (ts *cli) Del(table string, wheres []FV) error {
	collection := ts.db.Collection(table)
	filter := make([]bson.E, 0, len(wheres))
	for _, where := range wheres {
		filter = append(filter, bson.E{Key: where.Field, Value: where.Value})
	}

	_, err := collection.DeleteMany(context.TODO(), filter)
	return err
}

func (ts *cli) start() error {
	dbName := viper.GetString(eng_cfg.MongoDBName)
	addrs := viper.GetStringSlice(eng_cfg.MongoAddr)
	user := viper.GetString(eng_cfg.MongoUser)
	psw := viper.GetString(eng_cfg.MongoPassword)
	// 设置MongoDB连接字符串
	uri := "mongodb://" //mongodb://[username[:password]@]node1:27017,node2:27017,node3:27017
	if len(user) > 0 {
		uri += user
	}
	if len(psw) > 0 {
		uri += ":" + psw
	}
	if len(user) > 0 || len(psw) > 0 {
		uri += "@"
	}
	for idx, addr := range addrs {
		uri += addr
		if idx < len(addrs)-1 {
			uri += ","
		}
	}
	clientOptions := options.Client().ApplyURI(uri).SetAppName(eng_cfg.AppNode).SetConnectTimeout(5 * time.Second).SetServerSelectionTimeout(10 * time.Second)
	// 连接到MongoDB
	client, err := mongo.Connect(context.TODO(), clientOptions)
	if err != nil {
		return err
	}

	// 检查连接
	err = client.Ping(context.TODO(), nil)
	if err != nil {
		return err
	}
	ts.db = client.Database(dbName)
	return nil
}

func (ts *cli) stop() {
	_ = ts.db.Client().Disconnect(context.TODO())
}
